package com.yonyou.crm.common.rest.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.util.WebUtils;

/**
 * Rest异常统一处理中心类
 *
 */
public class CrmRestExceptionHandler extends AbstractHandlerExceptionResolver implements InitializingBean {
	private static final Logger log = LoggerFactory.getLogger(CrmRestExceptionHandler.class);
    private HttpMessageConverter<?>[] messageConverters = null;
    private List<HttpMessageConverter<?>> allMessageConverters = null;
    private RestErrorResolver errorResolver;
    private RestErrorConverter<?> errorConverter;
   
    /**
     * 服务启动时执行
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        ensureMessageConverters();
    }

    @SuppressWarnings("unchecked")
    private void ensureMessageConverters() {
        List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
        if (this.messageConverters != null && this.messageConverters.length > 0) {
            converters.addAll(CollectionUtils.arrayToList(this.messageConverters));
        }
        new HttpMessageConverterHelper().addDefaults(converters);
        this.allMessageConverters = converters;
    }

    private static final class HttpMessageConverterHelper extends WebMvcConfigurationSupport {
        public void addDefaults(List<HttpMessageConverter<?>> converters) {
            addDefaultHttpMessageConverters(converters);
        }
    }

    /**
     * 入口：处理程序抛出的异常,最终返回json格式的错误信息
     */
    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        RestErrorResolver resolver = getErrorResolver();//获取异常处理器CrmDefaultRestErrorResolver
        CrmRestError error = resolver.resolveError(webRequest, handler, ex);//根据异常信息，通过处理器处理得到与其匹配的CrmRestError对象(实为json传格式)
        if (error == null) {
            return null;
        }

        ModelAndView mav = null;
        try {
            mav = getModelAndView(webRequest, handler, error);//把CrmRestError对象转为json格式数据，并放在response的body中
        } catch (Exception invocationEx) {
            log.error("Acquiring ModelAndView for Exception [" + ex + "] resulted in an exception.", invocationEx);
        }
        return mav;
    }

    /**
     * 把CrmRestError对象转为json格式数据，并放在response的body中
     * @param webRequest
     * @param handler
     * @param error
     * @return
     * @throws Exception
     */
    protected ModelAndView getModelAndView(ServletWebRequest webRequest, Object handler, CrmRestError error) throws Exception {

        applyStatusIfPossible(webRequest, error);//根据异常信息，设置响应Response的状态码
        Object body = error; //
        RestErrorConverter converter = getErrorConverter();//获取异常信息格式转换器
        if (converter != null) {
            body = converter.convert(error);//把CrmRestError对象转为map格式
        }
        return handleResponseBody(body, webRequest);
    }
    //设置响应的状态码
    private void applyStatusIfPossible(ServletWebRequest webRequest, CrmRestError error) {
        if (!WebUtils.isIncludeRequest(webRequest.getRequest())) {
            webRequest.getResponse().setStatus(error.getStatus().value());
        }
    }

    /**
     * 处理响应json格式数据
     * @param body
     * @param webRequest
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    private ModelAndView handleResponseBody(Object body, ServletWebRequest webRequest) throws ServletException, IOException {

        HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());

        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
        if (acceptedMediaTypes.isEmpty()) {
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
        }
        MediaType.sortByQualityValue(acceptedMediaTypes);
        HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
        Class<?> bodyType = body.getClass();
        List<HttpMessageConverter<?>> converters = this.allMessageConverters;
        if (converters != null) {
            for (MediaType acceptedMediaType : acceptedMediaTypes) {
                for (HttpMessageConverter messageConverter : converters) {
                    if (messageConverter.canWrite(bodyType, acceptedMediaType)) {
                    	//把json数据返回
                        messageConverter.write(body, acceptedMediaType, outputMessage);
                        //返回一个空的视图，让spring知晓
                        return new ModelAndView();
                    }
                }
            }
        }

        if (logger.isWarnEnabled()) {
            logger.warn("Could not find HttpMessageConverter that supports return type [" + bodyType +
                    "] and " + acceptedMediaTypes);
        }
        return null;
    }
    
    public CrmRestExceptionHandler() {
        this.errorResolver = new CrmDefaultRestErrorResolver();
        this.errorConverter = new MapRestErrorConverter();
    }

    public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
        this.messageConverters = messageConverters;
    }

    public void setErrorResolver(RestErrorResolver errorResolver) {
        this.errorResolver = errorResolver;
    }

    public RestErrorResolver getErrorResolver() {
        return this.errorResolver;
    }

    public RestErrorConverter<?> getErrorConverter() {
        return errorConverter;
    }

    public void setErrorConverter(RestErrorConverter<?> errorConverter) {
        this.errorConverter = errorConverter;
    }
}
